package br.ufpe.cin.protocolo;

import java.net.DatagramPacket;
import java.net.InetAddress;

public class RPFTPacket {

	public static final int comprimentoCabecalho = 13;
	public static final int SpecialPacketLength = comprimentoCabecalho + 1;
	private int numeroSequencia;
	private int numeroReconhecimento;
	private int janelaRecepcao;
	private int comprimentoDados;
	private boolean SYN;
	private boolean ACK;
	private boolean FIN;
	private boolean fileEnd;
	private boolean reconhecido;
	private boolean fileStart;
	private byte dados[];

	public static enum SpecialPacket {
		ACK, SYN, SYNACK, FIN, FINACK, FILE_END, FILE_START
	}

	public RPFTPacket(byte[] packet) {
		this.numeroReconhecimento = this.numeroSequencia 
				= this.janelaRecepcao = 0;
		
		for(int i = 0; i < 4; i++) {
			this.numeroSequencia = (this.numeroSequencia << 8) + (packet[i] & 0xff);
			this.numeroReconhecimento = (this.numeroReconhecimento << 8) + (packet[i+4] & 0xff);
		}

		this.janelaRecepcao = ((packet[8] & 0xff) << 8) + (packet[9] & 0xff);
		this.comprimentoDados = ((packet[10] & 0xff) << 8) + (packet[11] & 0xff);
		
		fileEnd = (packet[12] % 2 != 0) ? true : false;
		FIN = ((packet[12] >> 1) % 2 != 0) ? true : false;
		ACK = ((packet[12] >> 2) % 2 != 0) ? true : false;
		SYN = ((packet[12] >> 3) % 2 != 0) ? true : false;
		reconhecido = false;
		
		this.dados = new byte[comprimentoDados];
		System.arraycopy(packet, comprimentoCabecalho, dados, 0, dados.length);
	}

	//Construtor para dados
	public RPFTPacket(byte[] data, int seqNum) {
		this.dados = (data != null) ? data : new byte[1];
		this.numeroSequencia = seqNum;
		this.comprimentoDados = this.dados.length;
		this.SYN = this.ACK = this.fileEnd = this.FIN = this.reconhecido 
				= false;
	}

	public RPFTPacket(SpecialPacket type, int numRec) {
		this.numeroReconhecimento = numRec;
		switch(type) {
		case SYN:
			this.SYN = true;
			this.ACK = this.fileEnd = this.FIN = false;
			break;
		case ACK:
			this.ACK = true;
			this.SYN = this.fileEnd = this.FIN = false;
			break;
		case SYNACK:
			this.SYN = this.ACK = true;
			this.fileEnd = this.FIN = false;
			break;
		case FIN:
			this.FIN = true;
			this.SYN = this.ACK = this.fileEnd = fileStart = false;
			break;
		case FINACK:
			this.FIN = this.ACK = true;
			this.SYN = this.fileEnd = fileStart = false;
			break;
		case FILE_END:
			this.fileEnd = true;
			this.SYN = this.ACK = this.FIN = fileStart = false;
			break;
		case FILE_START:
			this.fileStart = true;
			SYN = ACK = FIN = fileEnd = false;
		}
		this.dados = new byte[1];
		this.comprimentoDados = 1;
	}
	
	public RPFTPacket(int recNum, int rwnd, int seqNum) {
		this.numeroReconhecimento = recNum;
		this.numeroSequencia = seqNum;
		this.janelaRecepcao = rwnd;
		this.ACK = true;
		this.SYN = this.fileEnd = this.FIN = false;
		this.dados = new byte[1];
		this.comprimentoDados = 1;
	}

	public DatagramPacket gerarDatagramaUDP() {
		return new DatagramPacket(this.toByteArray(), comprimentoCabecalho + dados.length);
	}
	
	public DatagramPacket gerarDatagramaUDP(InetAddress addr, int port) {
		return new DatagramPacket(this.toByteArray(), comprimentoCabecalho + dados.length, addr, port);
	}

	public byte[] getDados() {
		return dados;
	}

	public int getJanelaRecepcao() {
		return janelaRecepcao;
	}

	public int getNumeroReconhecimento() {
		return numeroReconhecimento;
	}

	public int getNumeroSequencia() {
		return numeroSequencia;
	}

	public boolean isACK() {
		return ACK;
	}

	public boolean isFIN() {
		return FIN;
	}

	public boolean isReconhecido() {
		return reconhecido;
	}

	public boolean isSYN() {
		return SYN;
	}

	public boolean isSYNACK() {
		return SYN && ACK;
	}
	
	public boolean isDataPacket() {
		return !(SYN || ACK || FIN || fileEnd);
	}
	
	public boolean isFINACK() {
		return FIN && ACK;
	}

	public int getComprimentoDados() {
		return comprimentoDados;
	}

	public boolean isFileEnd() {
		return fileEnd;
	}

	public void reconhecer() {
		this.reconhecido = true;
	}

	public byte[] toByteArray() {
		byte[] retorno = new byte[comprimentoCabecalho + this.dados.length];

		retorno[0] = (byte) (this.numeroSequencia >> 24);
		retorno[1] = (byte) (this.numeroSequencia >> 16);
		retorno[2] = (byte) (this.numeroSequencia >> 8);
		retorno[3] = (byte) this.numeroSequencia;
		
		retorno[4] = (byte) (this.numeroReconhecimento >> 24);
		retorno[5] = (byte) (this.numeroReconhecimento >> 16);
		retorno[6] = (byte) (this.numeroReconhecimento >> 8);
		retorno[7] = (byte) this.numeroReconhecimento;

		retorno[8] = (byte) (this.janelaRecepcao >> 8);
		retorno[9] = (byte) (this.janelaRecepcao);

		retorno[10] = (byte) (this.comprimentoDados >> 8);
		retorno[11] = (byte) (this.comprimentoDados);

		retorno[12] = (byte) ((SYN) ? 1 : 0);
		retorno[12] = (byte) ((retorno[12] << 1) + ((ACK) ? 1 : 0));
		retorno[12] = (byte) ((retorno[12] << 1) + ((FIN) ? 1 : 0));
		retorno[12] = (byte) ((retorno[12] << 1) + ((fileEnd) ? 1 : 0));

		System.arraycopy(dados, 0, retorno, comprimentoCabecalho, dados.length);

		return retorno;
	}

	public static void main(String[] args) {
		String s = "oi";
		RPFTPacket p1 = new RPFTPacket(s.getBytes(), 28);
		RPFTPacket p2 = new RPFTPacket(p1.toByteArray());

		System.out.println(p1.getJanelaRecepcao() == p2.getJanelaRecepcao());
		System.out.println(p1.getNumeroReconhecimento() == p2.getNumeroReconhecimento());
		System.out.println(p1.getNumeroSequencia() == p2.getNumeroSequencia());
		System.out.println(p1.isACK() == p2.isACK());
		System.out.println(p1.isFIN() == p2.isFIN());
		System.out.println(p1.isSYN() == p2.isSYN());
		System.out.println(new String(p1.getDados()));
		System.out.println(new String(p2.getDados()));
	}

	public void printarDados() {
		System.out.println("Syn? "+SYN);
		System.out.println("Ack? "+ACK);
		System.out.println("Fin? "+FIN);
		System.out.println("FileEnd? "+fileEnd);
	}
}